#region License
// Product:
// MCM (Message-Command-Message)
// mcm-framework.net
// 
// Author:
// Pawel Idzikowski
// Cologne, Germany
// idzikowski@polenter.com
// 
// This software is provided 'as-is', without any express or implied warranty.
// In no event will the author(s) be held liable for any damages arising from
// the use of this software.
// 
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it
// freely, subject to the following restrictions:
// 
//     1. The origin of this software must not be misrepresented; you must not
//       claim that you wrote the original software. If you use this software
//       in a product, an acknowledgment in the product documentation would be
//       appreciated but is not required.
// 
//     2. Altered source versions must be plainly marked as such, and must not
//       be misrepresented as being the original software.
// 
//     3. This notice may not be removed or altered from any source distribution.
// 
#endregion
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Runtime.CompilerServices;

namespace Polenter.Mcm
{
    ///<summary>
    /// Contains components. Components communicate using messages. 
    /// This class is thread safe.
    /// Create your custom ComponentContainer inherited from this one.
    /// Create message channels and services as properties of your ComponentContainer inheritor.
    ///</summary>
    public abstract class ComponentContainer : IDisposable
    {
        private readonly ComponentInfoCollection _componentInfos;
        private readonly MessageBroadcast _messageBroadcast;
        private readonly MessageChannelCollection _messageChannels;

        ///<summary>
        /// Creates message channel list from properties and fields using reflection.
        /// starts internal threads for message queing.
        ///</summary>
        protected ComponentContainer()
        {
            _componentInfos = new ComponentInfoCollection();
            _messageBroadcast = new MessageBroadcast();

            MessageChannelFactory messageChannelFactory = new MessageChannelFactory();
            _messageChannels = messageChannelFactory.CreateMessageChannels(this);

            _messageChannels.MessageArrived += on_messageChannels_MessageArrived;

            openMessageChannels();
        }

        ///<summary>
        ///</summary>
        internal MessageChannelCollection MessageChannels
        {
            get { return _messageChannels; }
        }

        ///<summary>
        /// Get component with the key
        ///</summary>
        ///<param name="key"></param>
        public object this[Type key]
        {
            get { return getComponentByKey(key); }
        }

        [MethodImpl(MethodImplOptions.Synchronized)]
        private object getComponentByKey(Type key)
        {
            return _componentInfos[key];
        }

        #region IDisposable Members

        ///<summary>
        /// All Message channels are closed.
        /// Components are not disposed. They should be disposed elsewhere.
        ///</summary>
        public void Dispose()
        {
            OnDisposing(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void OnDisposing(bool manualDisposing)
        {
            // Dispose Messages
            closeMessageChannels();
        }

        ~ComponentContainer()
        {
            OnDisposing(false);
        }


        #endregion

        ///<summary>
        /// Post message asynchronously to the channel with the selected name
        ///</summary>
        ///<param name="message"></param>
        ///<param name="channelName"></param>
        ///<exception cref="ArgumentNullException"></exception>
        ///<exception cref="InvalidOperationException">when the channel with the name cannot be found</exception>
        public void PostMessage(object message, string channelName)
        {
            if (message == null) throw new ArgumentNullException("message");
            if (channelName == null) throw new ArgumentNullException("channelName");
            if (!_messageChannels.Contains(channelName))
            {
                throw new InvalidOperationException(string.Format("ChannelName \"{0}\" cannot be found.", channelName));
            }

            MessageChannel channel = _messageChannels[channelName];
            channel.Post(message);
        }

        private void openMessageChannels()
        {
            foreach (MessageChannel messageChannel in MessageChannels)
            {
                if (!messageChannel.IsOpened)
                {
                    messageChannel.Open();
                }
            }
        }

        private void closeMessageChannels()
        {
            foreach (MessageChannel message in MessageChannels)
            {
                message.Close();
            }
        }

        private void on_messageChannels_MessageArrived(object sender, MessageEventArgs e)
        {
            _messageBroadcast.Broadcast(e.Message, e.MessageChannel.Name);
        }

        ///<summary>
        /// Add component with the type as the key
        ///</summary>
        ///<param name="component"></param>
        ///<typeparam name="TKey"></typeparam>
        public void Add<TKey>(TKey component) where TKey : class
        {
            if (component == null) throw new ArgumentNullException("component");
            Add(component, component.GetType());
        }

        ///<summary>
        /// Add component with the type as the key
        ///</summary>
        ///<param name="component"></param>
        ///<param name="key"></param>
        [MethodImpl(MethodImplOptions.Synchronized)]
        public void Add(object component, Type key)
        {
            if (component == null) throw new ArgumentNullException("component");
            if (key == null) throw new ArgumentNullException("key");
            _messageBroadcast.RegisterComponent(component);
            _componentInfos.Add(new ComponentInfo(component, key));
        }

        ///<summary>
        /// Remove component by its key
        ///</summary>
        ///<typeparam name="TKey"></typeparam>
        ///<returns>true if sth was removed</returns>
        public bool Remove<TKey>()
        {
            return Remove(typeof (TKey));
        }

        ///<summary>
        /// Remove component by its key
        ///</summary>
        ///<param name="key"></param>
        ///<returns>true if sth was removed</returns>
        [MethodImpl(MethodImplOptions.Synchronized)]
        public bool Remove(Type key)
        {
            if (_componentInfos.Contains(key))
            {
                ComponentInfo componentInfo = _componentInfos[key];
                _messageBroadcast.UnregisterComponent(componentInfo.Component);
                _componentInfos.Remove(key);
                return true;
            }
            return false;
        }

        ///<summary>
        /// Clears the component list
        ///</summary>
        [MethodImpl(MethodImplOptions.Synchronized)]
        public void Clear()
        {
            _componentInfos.Clear();
            _messageBroadcast.Clear();
        }

        ///<summary>
        /// Checks if component with the key exists
        ///</summary>
        ///<typeparam name="TKey"></typeparam>
        ///<returns></returns>
        public bool Contains<TKey>()
        {
            return Contains(typeof (TKey));
        }

        /// <summary>
        /// Check if a component with the key exists
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        [MethodImpl(MethodImplOptions.Synchronized)]
        public bool Contains(Type key)
        {
            return _componentInfos.Contains(key);
        }

        ///<summary>
        /// Get first found component implementing the type or null if the component was not found
        ///</summary>
        ///<typeparam name="TComponent"></typeparam>
        ///<returns></returns>
        [MethodImpl(MethodImplOptions.Synchronized)]
        public TComponent FirstOrDefault<TComponent>() where TComponent : class
        {
            foreach (ComponentInfo componentInfo in _componentInfos)
            {
                var component = componentInfo.Component as TComponent;
                if (component != null)
                {
                    return component;
                }
            }
            return null;
        }

        ///<summary>
        /// Get first found component implementing the type. If not found, an exception will be thrown.
        ///</summary>
        ///<typeparam name="TComponent"></typeparam>
        ///<returns></returns>
        ///<exception cref="InvalidOperationException"></exception>
        [MethodImpl(MethodImplOptions.Synchronized)]
        public TComponent First<TComponent>() where TComponent : class
        {
            var component = FirstOrDefault<TComponent>();
            if (component != null)
            {
                return component;
            }
            string mes = string.Format("Component of type \"{0}\" cannot be found.", typeof (TComponent));
            throw new InvalidOperationException(mes);
        }

        ///<summary>
        /// Get all components implementing the type, eg. all IDisposable components
        ///</summary>
        ///<typeparam name="TComponent"></typeparam>
        ///<returns></returns>
        [MethodImpl(MethodImplOptions.Synchronized)]
        public TComponent[] Many<TComponent>() where TComponent : class
        {
            var result = new List<TComponent>();
            foreach (ComponentInfo componentInfo in _componentInfos)
            {
                var component = componentInfo.Component as TComponent;
                if (component != null)
                {
                    result.Add(component);
                }
            }
            return result.ToArray();
        }
    }
}